using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GridHelper
{
    public enum NumberState : short
    {
        Empty = 0x0,
        Repeated = 0x1,
        Generted = 0x2,
        Placed = 0x4,
        GenertedAndRepeated = Repeated | Generted,
        PlacedAndRepeated = Repeated | Placed
    }

    private int[] m_Grid;
    private int[] m_AnswerGrid;
    private int[][] m_WeightArray;
    private NumberState[] m_NumberState;

    public GridGenerator Generator;

    #region Attribute
    public int[] Grid => m_Grid;

    public int[] AnswerGrid => m_AnswerGrid;

    public int GetGridNum(int row, int col)
    {
        return m_Grid[row * GridGenerator.GRID_SIZE + col];
    }

    public int GetGridNum(Vector2Int coordinate)
    {
        return GetGridNum(coordinate.x, coordinate.y);
    }

    public int GetAnsNum(int row, int col)
    {
        return m_AnswerGrid[row * GridGenerator.GRID_SIZE + col];
    }

    public int GetAnsNum(Vector2Int coordinate)
    {
        return GetAnsNum(coordinate.x, coordinate.y);
    }
    #endregion

    public GridHelper()
    {
        Reset();
    }

    #region About grid generator
    /// <summary>
    /// Generate new grid
    /// </summary>
    public void Reset()
    {
        if (Generator == null)
        {
            Generator = new GridGenerator();
        }

        Settings.ReadSettings();

        Generator.DigMethod = Settings.SetDigMethod;
        Generator.RemainNumberLSL = Settings.RemainNumberLSL;
        Generator.RemainNumberUSL = Settings.RemainNumberUSL;

        m_Grid = Generator.GenerateGrid();
        m_AnswerGrid = Generator.AnswerGrid;
        m_WeightArray = GridSolver.GetWeightArray(m_Grid);
        m_NumberState = new NumberState[GridGenerator.GRID_SIZE * GridGenerator.GRID_SIZE];

        for (int i = 0; i < GridGenerator.GRID_SIZE; i++)
        {
            for (int j = 0; j < GridGenerator.GRID_SIZE; j++)
            {
                if (m_Grid[i * GridGenerator.GRID_SIZE + j] != 0)
                    m_NumberState[i * GridGenerator.GRID_SIZE + j] = NumberState.Generted;
            }
        }
    }

    /// <summary>
    /// This setting is about the method of generating grid
    /// </summary>
    /// <param name="digMethod"></param>
    /// <param name="remainLSL">-1 means to don't change value</param>
    /// <param name="remainUSL">-1 means to don't change value</param>
    public void ChangeSetting(DigMethod digMethod, int remainLSL = -1, int remainUSL = -1)
    {
        Generator.DigMethod = digMethod;
        if (remainLSL != -1)
            Generator.RemainNumberLSL = remainLSL;
        if (remainUSL != -1)
            Generator.RemainNumberUSL = remainUSL;
    }
    #endregion

    public bool CheckNumberOnPlace(int num, int row, int col)
    {
        return num >=1 && num <= 9 && GridSolver.CanPlaceNumber(m_Grid, m_WeightArray, row * GridGenerator.GRID_SIZE + col, 1 << (num - 1));
    }

    public bool CheckGridIsAnswer()
    {
        for (int i = 0; i < m_Grid.Length; i++)
        {
            if (m_Grid[i] != m_AnswerGrid[i])
                return false;
        }
        return true;
    }

    #region Number Action
    /// <summary>
    /// Place a number and check the repeat numbers
    /// </summary>
    /// <param name="num"></param>
    /// <param name="row"></param>
    /// <param name="col"></param>
    /// <returns>Repeat numbers' coordiante</returns>
    public OperateNumberResult PlaceNumber(int num, int row, int col)
    {
        int n = row * GridGenerator.GRID_SIZE + col;
        OperateNumberResult result = new OperateNumberResult(m_Grid[n] == 0 ? PlaceState.PlaceNumberSuccess : PlaceState.PlaceNumberFail);
        // find repeat numbers' position
        if (!CheckNumberOnPlace(num, row, col))
        {
            if (result.PlaceState == PlaceState.PlaceNumberFail)
                return result;

            RepeatNumberList numberList = new RepeatNumberList();
            FindRepeatNumber(num, row, col, numberList);

            result.ImportRepeatNumber(numberList);
        }

        int b = 1 << (num - 1);
        m_Grid[n] = num;
        m_WeightArray[0][n / GridGenerator.GRID_SIZE] |= b;
        m_WeightArray[1][n % GridGenerator.GRID_SIZE] |= b;
        m_WeightArray[2][n / GridGenerator.GRID_SIZE / 3 * 3 + n % GridGenerator.GRID_SIZE / 3] |= b;
        return result;
    }

    /// <summary>
    /// Remove a number and check repeat numbers
    /// </summary>
    /// <param name="row"></param>
    /// <param name="col"></param>
    /// <returns>The coordinate of numbers that is same on removed number</returns>
    public OperateNumberResult RemoveNumber(int row, int col)
    {
        int n = row * GridGenerator.GRID_SIZE + col;

        OperateNumberResult result = new OperateNumberResult(
            m_Grid[n] != 0 && (m_NumberState[n] & NumberState.Generted) != NumberState.Generted
                ? PlaceState.RemoveNumberSuccess : PlaceState.RemoveNumberFail);

        if (m_Grid[n] != 0)
        {
            if (result.PlaceState == PlaceState.RemoveNumberFail)
                return result;

            int num = m_Grid[n];
            int b = 1 << (num - 1);
            m_Grid[n] = 0;
            m_NumberState[n] = NumberState.Empty;

            RepeatNumberList numberList = new RepeatNumberList();
            FindRepeatNumber(num, row, col, numberList);

            result.ImportRepeatNumber(numberList);

            // if has number, remove repeated state
            // row check
            if (result.RepeatNumber.Count <= 0)
                m_WeightArray[0][n / GridGenerator.GRID_SIZE] ^= b;
            else if (result.RepeatNumber.Count == 1)
            {
                int index = result.RepeatNumber[0].x * GridGenerator.GRID_SIZE + result.RepeatNumber[0].y;
                m_NumberState[index] ^= NumberState.Repeated;
            }
            // colum check
            if (numberList.ColRepeatNumber.Count <= 0)
                m_WeightArray[1][n % GridGenerator.GRID_SIZE] ^= b;
            else if (numberList.ColRepeatNumber.Count == 1)
            {
                int index = numberList.ColRepeatNumber[0].x * GridGenerator.GRID_SIZE + numberList.ColRepeatNumber[0].y;
                m_NumberState[index] ^= NumberState.Repeated;
            }
            // cell check
            if (numberList.CellRepeatNumber.Count <= 0)
                m_WeightArray[2][n / GridGenerator.GRID_SIZE / 3 * 3 + n % GridGenerator.GRID_SIZE / 3] ^= b;
            else if (numberList.CellRepeatNumber.Count == 1)
            {
                int index = numberList.CellRepeatNumber[0].x * GridGenerator.GRID_SIZE + numberList.CellRepeatNumber[0].y;
                m_NumberState[index] ^= NumberState.Repeated;
            }
        }

        return result;
    }
    #endregion

    void FindRepeatNumber(int num, int row, int col, RepeatNumberList result)
    {
        if (num == 0)
            return;
        for (int inRow = 0; inRow < GridGenerator.GRID_SIZE; inRow++)
        {
            if (m_Grid[inRow * GridGenerator.GRID_SIZE + col] == num)
            {
                result.RowRepeatNumber.Add(new Vector2Int(inRow, col));
                m_NumberState[inRow * GridGenerator.GRID_SIZE + col] |= NumberState.Repeated;
            }
        }

        for (int inCol = 0; inCol < GridGenerator.GRID_SIZE; inCol++)
        {
            if (m_Grid[row * GridGenerator.GRID_SIZE + inCol] == num && inCol != col)
            {
                result.ColRepeatNumber.Add(new Vector2Int(row, inCol));
                m_NumberState[row * GridGenerator.GRID_SIZE + inCol] |= NumberState.Repeated;
            }
        }

        int n = row * GridGenerator.GRID_SIZE + col;
        int cellXStart = n / GridGenerator.GRID_SIZE / 3 * 3;
        int cellyStart = n % GridGenerator.GRID_SIZE / 3 * 3;

        for (int i = cellXStart; i < cellXStart + 3; i++)
        {
            for (int j = cellyStart; j < cellyStart + 3; j++)
            {
                if (m_Grid[i * GridGenerator.GRID_SIZE + j] == num && i != row && j != col)
                {
                    result.CellRepeatNumber.Add(new Vector2Int(i, j));
                    m_NumberState[i * GridGenerator.GRID_SIZE + j] |= NumberState.Repeated;
                }
            }
        }
    }
}